﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/16 10:21:15
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Measure;

namespace ImageK.Process
{
	/** 8-bit image statistics, including histogram. */
	public class ByteStatistics : ImageStatistics
	{

	    /** Construct an ImageStatistics object from a ByteProcessor
		    using the standard measurement options (area, mean,
		    mode, min and max) and no calibration. */
	    public ByteStatistics(ImageProcessor ip): this(ip, AREA + MEAN + MODE + MIN_MAX, null)
	    {
		    
	    }

	    /** Constructs a ByteStatistics object from a ByteProcessor using
		    the specified measurement and calibration. */
	    public ByteStatistics(ImageProcessor ip, int mOptions, Calibration cal)
	    {
		    ByteProcessor bp = (ByteProcessor)ip;
		    Histogram = bp.getHistogram();
		    setup(ip, cal);
		    double minT = ip.getMinThreshold();
		    int minThreshold, maxThreshold;
		    bool limitToThreshold = (mOptions & LIMIT) != 0;
		    if (!limitToThreshold || minT == ImageProcessor.NO_THRESHOLD)
		    {
			    minThreshold = 0;
			    maxThreshold = 255;
		    }
		    else
		    {
			    minThreshold = (int)minT;
			    maxThreshold = (int)ip.getMaxThreshold();
		    }
		    if (limitToThreshold)
			    saveThreshold(minThreshold, maxThreshold, cal);
		    float[] cTable = cal != null ? cal.getCTable() : null;
		    if (cTable != null)
			    getCalibratedStatistics(minThreshold, maxThreshold, cTable);
		    else
			    getRawStatistics(minThreshold, maxThreshold);
		    if ((mOptions & MIN_MAX) != 0)
		    {
			    if (pixelCount == 0)
				    min = max = Double.NaN;
			    else if (cTable != null)
				    getCalibratedMinAndMax(minThreshold, maxThreshold, cTable);
			    else
				    getRawMinAndMax(minThreshold, maxThreshold);
		    }
		    if ((mOptions & ELLIPSE) != 0 || (mOptions & SHAPE_DESCRIPTORS) != 0)
			    fitEllipse(ip, mOptions);
		    else if ((mOptions & CENTROID) != 0)
			    getCentroid(ip, minThreshold, maxThreshold);
		    if ((mOptions & (CENTER_OF_MASS | SKEWNESS | KURTOSIS)) != 0)
			    calculateMoments(ip, minThreshold, maxThreshold, cTable);
		    if ((mOptions & MEDIAN) != 0)
			    calculateMedian(Histogram, minThreshold, maxThreshold, cal);
		    if ((mOptions & AREA_FRACTION) != 0)
			    calculateAreaFraction(ip, Histogram);
	    }

	    void getCalibratedStatistics(int minThreshold, int maxThreshold, float[] cTable)
	    {
		    int count;
		    double value;
		    double sum = 0;
		    double sum2 = 0.0;
		    double isum = 0.0;

		    for (int i = minThreshold; i <= maxThreshold; i++)
		    {
			    count = Histogram[i];
			    value = cTable[i];
			    if (count > 0 && !double.IsNaN(value))
			    {
				    pixelCount += count;
				    sum += value * count;
				    isum += i * count;
				    sum2 += (value * value) * count;
				    if (count > maxCount)
				    {
					    maxCount = count;
					    mode = i;
				    }
			    }
		    }
		    area = pixelCount * pw * ph;
		    mean = sum / pixelCount;
		    umean = isum / pixelCount;
		    dmode = cTable[mode];
		    calculateStdDev(pixelCount, sum, sum2);
		    histMin = 0.0;
		    histMax = 255.0;
	    }

	    void getCentroid(ImageProcessor ip, int minThreshold, int maxThreshold)
	    {
		    byte[] pixels = (byte[])ip.getPixels();
		    byte[] mask = ip.getMaskArray();
		    bool limit = minThreshold > 0 || maxThreshold < 255;
		    double xsum = 0, ysum = 0;
		    int count = 0, i, mi, v;
		    for (int y = ry, my = 0; y < (ry + rh); y++, my++)
		    {
			    i = y * width + rx;
			    mi = my * rw;
			    for (int x = rx; x < (rx + rw); x++)
			    {
				    if (mask == null || mask[mi++] != 0)
				    {
					    if (limit)
					    {
						    v = pixels[i] & 255;
						    if (v >= minThreshold && v <= maxThreshold)
						    {
							    count++;
							    xsum += x;
							    ysum += y;
						    }
					    }
					    else
					    {
						    count++;
						    xsum += x;
						    ysum += y;
					    }
				    }
				    i++;
			    }
		    }
		    xCentroid = xsum / count + 0.5;
		    yCentroid = ysum / count + 0.5;
		    if (cal != null)
		    {
			    xCentroid = cal.getX(xCentroid);
			    yCentroid = cal.getY(yCentroid, height);
		    }
	    }

	    void calculateMoments(ImageProcessor ip, int minThreshold, int maxThreshold, float[] cTable)
	    {
		    byte[] pixels = (byte[])ip.getPixels();
		    byte[] mask = ip.getMaskArray();
		    int v, i, mi;
		    double dv, dv2, sum1 = 0.0, sum2 = 0.0, sum3 = 0.0, sum4 = 0.0, xsum = 0.0, ysum = 0.0;
		    for (int y = ry, my = 0; y < (ry + rh); y++, my++)
		    {
			    i = y * width + rx;
			    mi = my * rw;
			    for (int x = rx; x < (rx + rw); x++)
			    {
				    if (mask == null || mask[mi++] != 0)
				    {
					    v = pixels[i] & 255;
					    if (v >= minThreshold && v <= maxThreshold)
					    {
						    dv = ((cTable != null) ? cTable[v] : v) + double.MinValue;
						    dv2 = dv * dv;
						    sum1 += dv;
						    sum2 += dv2;
						    sum3 += dv * dv2;
						    sum4 += dv2 * dv2;
						    xsum += x * dv;
						    ysum += y * dv;
					    }
				    }
				    i++;
			    }
		    }
		    double mean2 = mean * mean;
		    double variance = sum2 / pixelCount - mean2;
		    double sDeviation = Math.Sqrt(variance);
		    skewness = ((sum3 - 3.0 * mean * sum2) / pixelCount + 2.0 * mean * mean2) / (variance * sDeviation);
		    kurtosis = (((sum4 - 4.0 * mean * sum3 + 6.0 * mean2 * sum2) / pixelCount - 3.0 * mean2 * mean2) / (variance * variance) - 3.0);
		    xCenterOfMass = xsum / sum1 + 0.5;
		    yCenterOfMass = ysum / sum1 + 0.5;
		    if (cal != null)
		    {
			    xCenterOfMass = cal.getX(xCenterOfMass);
			    yCenterOfMass = cal.getY(yCenterOfMass, height);
		    }
	    }

	    void getCalibratedMinAndMax(int minThreshold, int maxThreshold, float[] cTable)
	    {
		    if (pixelCount == 0)
		    { min = 0.0; max = 0.0; return; }
		    min = double.MaxValue;
		    max = -double.MaxValue;
		    double v = 0.0;
		    for (int i = minThreshold; i <= maxThreshold; i++)
		    {
			    if (Histogram[i] > 0)
			    {
				    v = cTable[i];
				    if (v < min)
					    min = v;
				    if (v > max)
					    max = v;
			    }
		    }
	    }

    }

}
